/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2021-2022. All rights reserved.
 *
 */

import { hvigorCore, Json5Reader, Project } from '@ohos/hvigor';
import * as fse from 'fs-extra';
import path from 'path';

import { findTargetProduct } from '../../common/find-target-product.js';
import { ohosTrace } from '../../common/trace/ohos-trace.js';
import { BuildProfileSchemaFileConst, CommonConst } from '../../const/common-const.js';
import { VersionConst } from '../../const/version-const.js';
import { ModuleType } from '../../enum/module-type-enum.js';
import { TargetRuntimeOS } from '../../options/build/module-build-profile.js';
import { ProjectBuildProfile } from '../../options/build/project-build-profile.js';
import { BuildModeManager } from '../../project/build-option/build-mode-manager.js';
import { parseApiVersion } from '../../sdk/sdk-util.js';
import { getElementFromArr } from '../../utils/array-util.js';
import { parseTargets } from '../../utils/command-line-util.js';
import { OhosLogger } from '../../utils/log/ohos-logger.js';
import { ValidateUtil } from '../../utils/validate/validate-util.js';
import { ModuleModel } from '../module/module-model.js';

import { ProjectModel } from './project-model.js';
import ProductApiMeta = ProjectBuildProfile.ProductApiMeta;

import { BuildArtifactExtension, BuildDirConst } from '../../const/build-directory-const.js';
import { ARKUIX_CONFIG_JSON5, ARKUIX_ROOT } from '../../const/crossplatform-const.js';
import { AkrUIXConfigJson } from '../../options/configure/akruix-config-opt.js';
import { TargetTaskService } from '../../tasks/service/target-task-service.js';
import { InjectUtil } from '../../utils/inject-util.js';

import { OhPackageJsonOpt, PackageJsonOpt } from '../../tasks/task/ohos-har-task.js';
import { getJson5Obj } from '../../utils/json-util.js';
import { ohPackageLoader } from '../../utils/loader/file/oh-package-loader';
import { projectBuildProfileLoader } from '../../utils/loader/file/project-build-profile-loader.js';
import { validataParameterKeys } from '../../validate/dependenct-param-validate.js';

/**
 * Stage或Fa模型的工程持久化数据模型,包含工程源码数据,配置数据等
 *
 * @since 2022/1/20
 */
export abstract class CoreProjectModelImpl implements ProjectModel {
  protected readonly project: Project;
  protected readonly projectPath: string;
  protected readonly name: string;
  protected readonly _profilePath: string;
  protected _profileOptions: ProjectBuildProfile.ProjectProfileOpt;
  protected _arkUIXConfigJsonObj: AkrUIXConfigJson.ConfigObj | undefined;
  protected _productNames: string[];
  protected _moduleSpecificTargets: Map<string, string[]> = new Map<string, string[]>();
  protected subModels = new Map<string, ModuleModel>();
  private _log = OhosLogger.getLogger(CoreProjectModelImpl.name);
  private productApiMetaMap: Map<string, ProductApiMeta> = new Map();

  // 存储所有模块target的runtimeOS信息
  protected targetRuntimeOS: TargetRuntimeOS[] = [];
  protected _moduleTargets: Map<string, TargetTaskService[]> = new Map();
  protected readonly parameterFileAbsolutePath: string | undefined;
  protected readonly parameterFileObj: object | undefined;
  protected readonly packageJsonObj: PackageJsonOpt | OhPackageJsonOpt;
  protected readonly paramWhiteList: string[] = ['version', 'devDependencies', 'dynamicDependencies', 'dependencies'];

  protected constructor(project: Project) {
    this.project = project;
    this.projectPath = project.getNodeDir();
    this.name = project.getName();
    this._profilePath = path.resolve(this.projectPath, CommonConst.PROFILE_JSON5);

    // project build-profile.json5 schema check
    this.projectBuildProfileCheck(this._profilePath);
    this._profileOptions = projectBuildProfileLoader.getBuildProfile();
    this._productNames = this._profileOptions.app.products!.map((product) => product.name);

    this.projectStatusCheck();
    this.initializeApiMetadata();

    this.parseConfiguredTargetsInHvigorCommand();
    this.initArkUIXConfigJsonObj();

    const projectPackagePath = this.isOhpmProject() ? this.getOhPackageJson5Path() : this.getPackageJsonPath();
    this.packageJsonObj = Json5Reader.getJson5Obj(projectPackagePath);
    if (!this.isOhpmProject()) {
      this.trace();
      return;
    }
    const configParameterFile = hvigorCore.getParameter().getExtParam(CommonConst.PARAMETER_FILE);
    if (configParameterFile && !this.packageJsonObj.parameterFile) {
      this._log.printErrorExit(
        'NO_PARAMETER_FILE_CONFIG',
        [projectPackagePath],
        [[projectPackagePath]]
      )
    }
    const parameterFile = configParameterFile ?? this.packageJsonObj.parameterFile;
    if (parameterFile) {
      this.parameterFileAbsolutePath = path.resolve(this.getProjectDir(), parameterFile);
      if (!fse.existsSync(this.parameterFileAbsolutePath)) {
        this._log.printErrorExit(
          'PARAMETER_FILE_NOT_EXIST',
          [parameterFile, projectPackagePath],
        )
      }
      if (!fse.statSync(this.parameterFileAbsolutePath).isFile()) {
        this._log.printErrorExit(
          'PARAMETER_FILE_NOT_FILE',
          [parameterFile, projectPackagePath],
        )
      }
      this.parameterFileObj = Json5Reader.getJson5Obj(this.parameterFileAbsolutePath);
      if (this.parameterFileObj) {
        validataParameterKeys(this.parameterFileObj);
      }
    }
    this.trace();
  }

  trace() {
    ohosTrace.traceBuildMode(BuildModeManager.getBuildMode());
  }

  refreshData(): void {
    this._profileOptions = projectBuildProfileLoader.getBuildProfile();
    this._productNames = this._profileOptions.app.products!.map((product) => product.name);

    this.projectStatusCheck();
    this.initializeApiMetadata();
  }

  registryTarget(service: TargetTaskService) {
    const module = service.getModuleService().getModuleModel().getName();
    const services = this._moduleTargets.get(module) ?? [];
    this._moduleTargets.set(module, [...services, service]);
  }

  getTarget(moduleName: string, targetName?: string) {
    if (!targetName) {
      return this._moduleTargets.get(moduleName)?.find(() => true);
    }
    return this._moduleTargets.get(moduleName)?.find((service) => service.getTargetData().getTargetName() === targetName);
  }

  getTargetRuntimeOSs() {
    return this.targetRuntimeOS;
  }

  abstract initSubProject(): void;

  private parseConfiguredTargetsInHvigorCommand() {
    const configModuleStr = hvigorCore.getExtraConfig().get('module');
    this._moduleSpecificTargets = parseTargets(configModuleStr);
  }

  private projectStatusCheck() {
    ValidateUtil.validateContainsDefault(this._profileOptions.app.products, this._profilePath);
    ValidateUtil.validateDuplicatedName(this._profileOptions.app.products, 'products', this._profilePath);
    ValidateUtil.validateDuplicatedName(this._profileOptions.app.signingConfigs, 'signingConfigs', this._profilePath);
    ValidateUtil.validateDuplicatedName(this._profileOptions.modules, 'modules', this._profilePath);
    this._profileOptions.modules.forEach((value) => ValidateUtil.validateDuplicatedName(value.targets, 'targets', this._profilePath));
    ValidateUtil.apiConfigurationCheck(this);
    ValidateUtil.modelVersionCheck();
  }

  /**
   * 初始化product的api版本信息
   *
   * @private
   */
  private initializeApiMetadata() {
    const currentProduct: ProjectBuildProfile.ProductBuildOpt = findTargetProduct(this);
    const isHarmonyOS = currentProduct.runtimeOS === CommonConst.HARMONY_OS;
    this.productApiMetaMap.set(currentProduct.name, {
      compileSdkVersion: parseApiVersion(currentProduct.compileSdkVersion ?? VersionConst.SUPPORT_COMPILE_VERSION, isHarmonyOS),
      compatibleSdkVersion: parseApiVersion(currentProduct.compatibleSdkVersion!, isHarmonyOS),
      targetSdkVersion: currentProduct.targetSdkVersion ? parseApiVersion(currentProduct.targetSdkVersion, isHarmonyOS) : undefined,
    });
  }

  /**
   * 获取该工程默认的bundleName值,即AppScope中配置的值
   */
  abstract getDefaultBundleName(): string;

  /**
   * 获取该工程默认的BundleType值
   */
  abstract getBundleType(): string;

  getBuildProfileName(): string {
    return CommonConst.PROFILE_JSON5;
  }

  getName(): string {
    return this.name;
  }

  getProject(): Project {
    return this.project;
  }

  getPackageJsonPath(): string {
    return path.resolve(this.projectPath, CommonConst.PACKAGE_JSON);
  }

  getOhPackageJson5Path(): string {
    return ohPackageLoader.getNodeOhPackagePath(path.resolve(this.projectPath, CommonConst.OH_PACKAGE_JSON5));
  }

  getArkUIXConfigJsonPath(): string {
    return path.resolve(this.projectPath, ARKUIX_ROOT, ARKUIX_CONFIG_JSON5);
  }

  getProfilePath(): string {
    return this._profilePath;
  }

  getProjectDir(): string {
    return this.projectPath;
  }

  getProductNames(): string[] {
    return this._productNames;
  }

  getProfileOpt(): ProjectBuildProfile.ProjectProfileOpt {
    return this._profileOptions;
  }

  getSubModuleModels(): Map<string, ModuleModel> {
    return this.subModels;
  }

  getModuleModelByName(moduleName: string): ModuleModel | undefined {
    return this.subModels.get(moduleName);
  }

  /**
   * 根据product获取compileSdkVersion
   * 不要早于ProjectInspection使用, 可能获取无效值
   *
   * @param product
   */
  getCompileApiMetaByProduct(product: string) {
    const opt: ProductApiMeta | undefined = this.getProductApiMeta(product);
    if (!opt) {
      throw new Error(`Can not find product ${product}.`);
    }
    return opt.compileSdkVersion;
  }

  /**
   * 根据product获取compatibleSdkVersion
   * 不要早于ProjectInspection使用, 可能获取无效值
   *
   * @param product
   */
  getCompatibleApiMetaByProduct(product: string) {
    const opt: ProductApiMeta | undefined = this.getProductApiMeta(product);
    if (!opt) {
      throw new Error(`Can not find product ${product}.`);
    }
    return opt.compatibleSdkVersion;
  }

  getProductApiMeta(product: string): ProjectBuildProfile.ProductApiMeta | undefined {
    return this.productApiMetaMap?.get(product);
  }

  getTargetApiMetaByProduct(product: string): ProjectBuildProfile.ApiMeta | undefined {
    return this.getProductApiMeta(product)?.targetSdkVersion;
  }

  getAllModules(): ModuleModel[] {
    return [...this.subModels.values()];
  }

  getAllEntryModules(): Set<string> {
    const entries = new Set<string>();
    this.subModels.forEach((value, key) => {
      if (value.getModuleType() === ModuleType.Entry) {
        entries.add(key);
      }
    });
    return entries;
  }

  /**
   * 获取根项目下build-profile.json5中的applyToProduct配置
   *
   * @param moduleName
   * @param targetName
   */
  getTargetApplyProducts(moduleName: string, targetName: string): string[] | undefined {
    const module = getElementFromArr(this._profileOptions.modules, moduleName);
    if (module === undefined) {
      return undefined;
    }
    const target = getElementFromArr(module.targets, targetName);
    if (target === undefined) {
      return undefined;
    }
    return target.applyToProducts;
  }

  /**
   * 根据moduleName获取build-profile.json5中的module配置
   *
   * @param moduleName
   */
  getModuleProfileOpt(moduleName: string): ProjectBuildProfile.ModuleBuildOpt | undefined {
    return getElementFromArr(this._profileOptions!.modules, moduleName);
  }

  /**
   * 获取命令行参数-p module=配置的module和targets信息
   */
  getModuleSpecificTargets(): Map<string, string[]> {
    return this._moduleSpecificTargets;
  }

  getParameterFileAbsolutePath(): string | undefined {
    return this.parameterFileAbsolutePath;
  }

  getParameterFileObj(): object | undefined {
    return this.parameterFileObj;
  }

  private projectBuildProfileCheck(projectBuildProfilePath: string): void {
    const projectSchemaPath: string = BuildProfileSchemaFileConst.PROJECT_BUILD_PROFILE_SCHEMA_PATH;
    const moduleSchemaPath: string = BuildProfileSchemaFileConst.HAP_MODULE_BUILD_PROFILE_SCHEMA_PATH;

    if (!fse.existsSync(projectBuildProfilePath)) {
      this._log.printErrorExit(
        'PROJECT_BUILD_PROFILE_NOT_FOUND',
        [projectBuildProfilePath],
        [[this.name]]
      )
    }

    // 如果校验有问题，考虑是否可能是工程结构错误，将module级别的build-profile.json5放在了project中
    if (
      ValidateUtil.submitSchemaCheckWork({
        moduleName: this.name,
        filePath: projectBuildProfilePath,
        schemaPath: moduleSchemaPath,
        checkOnly: true,
      })
    ) {
      this._log.printErrorExit(
        'PROJECT_BUILD_PROFILE_NOT_COMPLY_WITH_SCHEMA',
        [],
        [[this.name],[path.resolve(this.projectPath, CommonConst.BUILD_FILE_NAME)]]
      )
    }
    ValidateUtil.submitSchemaCheckWork({
      moduleName: this.name,
      filePath: projectBuildProfilePath,
      schemaPath: projectSchemaPath,
    });

    if (projectBuildProfileLoader.getBuildProfile().app.products === undefined) {
      this._log.printErrorExit(
        'PRODUCT_NOT_FOUND',
        [this._profilePath],
        [[this._profilePath]]
      )
    }
  }

  /**
   * 判断该工程是否为ohpm工程:
   * api高于9且工程目录下存在oh-package.json5(和工程管理逻辑保持一致)
   * api高于9且不存在存在oh-package.json5也不存在package.json，默认是ohpm工程
   *
   * @returns true or false
   */
  isOhpmProject(): boolean {
    return (
      this.getCompileApiMetaByProduct(findTargetProduct(this).name).version >= 9 &&
      (fse.existsSync(this.getOhPackageJson5Path()) || !fse.existsSync(this.getPackageJsonPath()))
    );
  }

  isCrossplatformProject(): boolean {
    if (!this._arkUIXConfigJsonObj) {
      return false;
    }
    return this._arkUIXConfigJsonObj.crossplatform;
  }

  initArkUIXConfigJsonObj(): void {
    const arkUIXConfigJsonPath = this.getArkUIXConfigJsonPath();
    if (!fse.existsSync(arkUIXConfigJsonPath)) {
      return;
    }

    ValidateUtil.submitSchemaCheckWork({
      moduleName: this.name,
      filePath: arkUIXConfigJsonPath,
      schemaPath: BuildProfileSchemaFileConst.ARKUI_X_CONFIG_SCHEMA_PATH,
    });

    this._arkUIXConfigJsonObj = getJson5Obj(arkUIXConfigJsonPath);
    this.arkUIXModuleCheck();
  }

  getArkUIXConfigJsonObj(): AkrUIXConfigJson.ConfigObj | undefined {
    return this._arkUIXConfigJsonObj;
  }

  private arkUIXModuleCheck(): void {
    const differentModuleName = this._arkUIXConfigJsonObj?.modules.reduce((result: string[], element) => {
      if (!this._profileOptions.modules.some((item) => item.name === element)) {
        result.push(element);
      }
      return result;
    }, []);

    if (!differentModuleName || !differentModuleName.length) {
      return;
    }

    this._log.printErrorExit(
      'INVALID_ARKUI_X_MODULE_NAME',
      [differentModuleName.join('","'), this.getArkUIXConfigJsonPath()],
      [[this.getArkUIXConfigJsonPath()]]
    )
  }

  /**
   * 获取远程仓hsp路径
   */
  getRemoteHspPath(): string {
    return path.resolve(this.getProjectDir(), CommonConst.OH_MODULES, BuildArtifactExtension.DOT_HSP);
  }

  /**
   * 获取工程级的签名remote-hsp存储目录
   * @param productName
   */
  getCacheRemoteHspPath(productName: string): string {
    const customBuildDir = path.resolve(InjectUtil.getBuildCacheParentDir(this.getProjectDir(), hvigorCore.getProject()!.getName()), BuildDirConst.BUILD_ROOT);
    return path.resolve(customBuildDir, CommonConst.CACHE, productName, CommonConst.REMOTE_HSP);
  }

  /**
   * 获取工程级的integrated_hsp存储目录
   * @param productName
   */
  getCacheIntegratedHspPath(productName: string): string {
    const customBuildDir = path.resolve(InjectUtil.getBuildCacheParentDir(this.getProjectDir(), hvigorCore.getProject()!.getName()), BuildDirConst.BUILD_ROOT);
    return path.resolve(customBuildDir, CommonConst.CACHE, productName, BuildDirConst.INTEGRATED_HSP);
  }

  isFaMode(): boolean {
    return false;
  }
}
